package org.lestr.secuel.sogl;

import java.util.ArrayList;
import javax.media.opengl.GL;
import javax.media.opengl.GLCanvas;

import org.lestr.secuel.m3ds.Affichage;
import org.lestr.secuel.m3ds.Camera;
import org.lestr.secuel.m3ds.Parametres;
import org.lestr.secuel.m3ds.Univers;
import org.lestr.secuel.m3ds.IUsine;
import org.lestr.secuel.m3ds.animations.AnimationObjets2D;
import org.lestr.secuel.m3ds.animations.AnimationObjets3D;
import org.lestr.secuel.m3ds.animations.CleObjet2D;
import org.lestr.secuel.m3ds.animations.CleObjet3D;
import org.lestr.secuel.m3ds.lumieres.Ampoule;
import org.lestr.secuel.m3ds.lumieres.LumiereAmbiante;
import org.lestr.secuel.m3ds.lumieres.Spot;
import org.lestr.secuel.m3ds.maths.IPoint3d;
import org.lestr.secuel.m3ds.maths.IPoint2d;
import org.lestr.secuel.m3ds.maths.IVecteur2d;
import org.lestr.secuel.m3ds.maths.IVecteur3d;
import org.lestr.secuel.m3ds.objets2D.IImage;
import org.lestr.secuel.m3ds.objets2D.IPixel;
import org.lestr.secuel.m3ds.objets3D.IDimensionObjet3d;
import org.lestr.secuel.m3ds.objets3D.IDimensionTexture;
import org.lestr.secuel.m3ds.objets3D.IPositionTexture;
import org.lestr.secuel.m3ds.objets3D.IApparence;
import org.lestr.secuel.m3ds.objets3D.Materiau;
import org.lestr.secuel.m3ds.objets3D.IObjet3d;
import org.lestr.secuel.m3ds.objets3D.IOrientationObjet3d;
import org.lestr.secuel.m3ds.objets3D.IPositionObjet3d;
import org.lestr.secuel.m3ds.objets3D.ISommet;
import org.lestr.secuel.m3ds.objets3D.ITriangle;
import org.lestr.secuel.sogl.lumieres.SOGLAmpoule;
import org.lestr.secuel.sogl.lumieres.SOGLLumiereAmbiante;
import org.lestr.secuel.sogl.lumieres.SOGLSpot;
import org.lestr.secuel.sogl.maths.Point2d;
import org.lestr.secuel.sogl.maths.Point3d;
import org.lestr.secuel.sogl.maths.Vecteur2d;
import org.lestr.secuel.sogl.maths.Vecteur3d;
import org.lestr.secuel.sogl.objets2D.SOGLImage;
import org.lestr.secuel.sogl.objets2D.SOGLPixel;
import org.lestr.secuel.sogl.objets3D.DimensionObjet3d;
import org.lestr.secuel.sogl.objets3D.DimensionTexture;
import org.lestr.secuel.sogl.objets3D.OrientationObjet3d;
import org.lestr.secuel.sogl.objets3D.PositionObjet3d;
import org.lestr.secuel.sogl.objets3D.PositionTexture;
import org.lestr.secuel.sogl.objets3D.SOGLMateriau;
import org.lestr.secuel.sogl.objets3D.Objet3D;
import org.lestr.secuel.sogl.objets3D.Triangle;
import org.lestr.secuel.sogl.objets3D.Sommet;
import org.lestr.secuel.sogl.objets3D.Texture;

public class Usine implements IUsine {


    // Variables statiques
    private static Usine singleton;


    /** Retourne le singleton de la classe */
    public static synchronized Usine getSingleton() {

        // Initialise si besoin
        if (singleton == null)
            singleton = new Usine();

        return singleton;

    }// FIN getSingleton


    // Déclaration des attributs
    private int nbLumièresMax;


    private Parametres paramètres;


    /** Constructeur */
    private Usine() {

        // Instanciation des attributs
        nbLumièresMax = 0;
        paramètres = null;

        SOGLAffichage.retSingleton();

        // Détermine le nombre maximal de lumière supporté
        int[] max = new int[1];
        GL gl = new GLCanvas().getGL();
        gl.glGetIntegerv(GL.GL_MAX_LIGHTS, max, 0);
        nbLumièresMax = 8;// TODO max[0];

    }// FIN Constructeur


    /** Retourne le nom du moteur 3D */
    @Override
    public String obtenirNom() {

        return "SOGL";

    }// FIN retNom


    /** Retourne les paramètres du moteur 3D */
    @Override
    public Parametres obtenirParamètres() {

        // Les initialises si pas encore fait
        if (paramètres == null)
            paramètres = new SOGLParametres();

        // Les retournes
        return paramètres;

    }// FIN retParamètres


    /** Retourne un point 2D */
    @Override
    public IPoint2d point2d() {

        return new Point2d(this);

    }// FIN point2d


    /** Retourne un point 2D */
    @Override
    public IPoint2d point2d(IPoint2d point2d) {

        return new Point2d(this, point2d);

    }// FIN point2d


    /** Retourne un point 2D initialisé */
    @Override
    public IPoint2d point2d(double x,
                            double y) {

        return new Point2d(this, x, y);

    }// FIN point2d


    /** Retourne un vecteur 2D */
    @Override
    public IVecteur2d vecteur2d() {

        return new Vecteur2d(this);

    }// FIN vecteur2d


    /** Retourne un vecteur 2D */
    @Override
    public IVecteur2d vecteur2d(IPoint2d point2d) {

        return new Vecteur2d(this, point2d);

    }// FIN vecteur2d


    /** Retourne un vecteur 2D initialisé */
    @Override
    public IVecteur2d vecteur2d(double x,
                                double y) {

        return new Vecteur2d(this, x, y);

    }// FIN vecteur2d


    /** Retourne un point 3D initialisé */
    @Override
    public ISommet sommet(ITriangle triangle) {

        return new Sommet(this, triangle);

    }// FIN sommet


    @Override
    public IPoint3d point3d() {

        return new Point3d(this);

    }// FIN point3d


    @Override
    public IPoint3d point3d(IPoint3d point3d) {

        return new Point3d(this, point3d);

    }// FIN point3d


    @Override
    public IPoint3d point3d(double x,
                            double y,
                            double z) {

        return new Point3d(this, x, y, z);

    }// FIN point3d


    /** Retourne un point 3D initialisé */
    @Override
    public ISommet sommet(ITriangle triangle,
                          double x,
                          double y,
                          double z) {

        return new Sommet(this, triangle, x, y, z);

    }// FIN sommet


    @Override
    public ISommet sommet(ITriangle triangle,
                          IPoint3d point3d) {

        return new Sommet(this, triangle, point3d);

    }// FIN sommet


    /** Retourne un point 3D initialisé */
    @Override
    public IPositionObjet3d positionObjet3d(IObjet3d objet3d) {

        return new PositionObjet3d(this, objet3d);

    }// FIN positionObjet3d


    /** Retourne un point 3D initialisé */
    @Override
    public IPositionObjet3d positionObjet3d(IObjet3d objet3d, IPoint3d point3d) {

        return new PositionObjet3d(this, objet3d, point3d);

    }// FIN positionObjet3d


    /** Retourne un point 3D initialisé */
    @Override
    public IPositionObjet3d positionObjet3d(IObjet3d objet3d,
                                double x,
                                double y,
                                double z) {

        return new PositionObjet3d(this, objet3d, x, y, z);

    }// FIN positionObjet3d


    /** Retourne un point 3D initialisé */
    @Override
    public IOrientationObjet3d orientationObjet3d(IObjet3d objet3d) {

        return new OrientationObjet3d(this, objet3d);

    }// FIN orientationObjet3d


    /** Retourne un point 3D initialisé */
    @Override
    public IOrientationObjet3d orientationObjet3d(IObjet3d objet3d, IPoint3d point3d) {

        return new OrientationObjet3d(this, objet3d, point3d);

    }// FIN orientationObjet3d


    /** Retourne un point 3D initialisé */
    @Override
    public IOrientationObjet3d orientationObjet3d(IObjet3d objet3d,
                                           double x,
                                           double y,
                                           double z) {

        return new OrientationObjet3d(this, objet3d, x, y, z);

    }// FIN orientationObjet3d


    /** Retourne un point 3D initialisé */
    @Override
    public IDimensionObjet3d dimensionObjet3d(IObjet3d objet3d) {

        return new DimensionObjet3d(this, objet3d);

    }// FIN dimensionObjet3d


    /** Retourne un point 3D initialisé */
    @Override
    public IDimensionObjet3d dimensionObjet3d(IObjet3d objet3d, IPoint3d point3d) {

        return new DimensionObjet3d(this, objet3d, point3d);

    }// FIN dimensionObjet3d


    /** Retourne un point 3D initialisé */
    @Override
    public IDimensionObjet3d dimensionObjet3d(IObjet3d objet3d,
                                       double x,
                                       double y,
                                       double z) {

        return new DimensionObjet3d(this, objet3d, x, y, z);

    }// FIN dimensionObjet3d


    /** Retourne un vecteur 2D */
    @Override
    public IVecteur3d vecteur3D() {

        return new Vecteur3d(this);

    }// FIN vecteur3D


    /** Retourne un vecteur 2D initialisé */
    @Override
    public IVecteur3d vecteur3D(double x,
                                double y,
                                double z) {

        return new Vecteur3d(this, x, y, z);

    }// FIN vecteur3D


    /** Retourne un triangle */
    @Override
    public ITriangle triangle() {

        return new Triangle(this);

    }// FIN triangle


    /** Retourne un triangle */
    @Override
    public ITriangle triangle(IPoint3d sommet1,
                              IPoint3d sommet2,
                              IPoint3d sommet3) {

        return new Triangle(this, sommet1, sommet2, sommet3);

    }// FIN triangle


    /** Défini la forme de l'objet comme un plan */
    @Override
    public IObjet3d plan() {

        IObjet3d résultat = objet3D();

        ITriangle triangle;

        triangle = triangle();
        triangle.obtenirSommet1().définirXYZ(-0.5f, 0f, -0.5f);
        triangle.obtenirSommet2().définirXYZ(-0.5f, 0f, 0.5f);
        triangle.obtenirSommet3().définirXYZ(0.5f, 0f, 0.5f);
        résultat.ajouterTriangle(triangle);

        triangle = triangle();
        triangle.obtenirSommet1().définirXYZ(-0.5f, 0f, -0.5f);
        triangle.obtenirSommet2().définirXYZ(0.5f, 0f, 0.5f);
        triangle.obtenirSommet3().définirXYZ(0.5f, 0f, -0.5f);
        résultat.ajouterTriangle(triangle);

        return résultat;

    }// FIN defPlan


    /** Défini la forme de l'objet comme un cube */
    @Override
    public IObjet3d cube() {

        IObjet3d résultat = objet3D();

        résultat.ajouterTriangle(triangle(point3d(-0.5f, -0.5f, 0.5f),
                                        point3d(-0.5f, 0.5f, 0.5f),
                                        point3d(0.5f, -0.5f, 0.5f)));
        résultat.ajouterTriangle(triangle(point3d(0.5f, -0.5f, 0.5f),
                                        point3d(-0.5f, 0.5f, 0.5f),
                                        point3d(0.5f, 0.5f, 0.5f)));
        résultat.ajouterTriangle(triangle(point3d(-0.5f, 0.5f, 0.5f),
                                        point3d(-0.5f, 0.5f, -0.5f),
                                        point3d(0.5f, 0.5f, -0.5f)));
        résultat.ajouterTriangle(triangle(point3d(0.5f, 0.5f, 0.5f),
                                        point3d(-0.5f, 0.5f, 0.5f),
                                        point3d(0.5f, 0.5f, -0.5f)));
        résultat.ajouterTriangle(triangle(point3d(0.5f, -0.5f, 0.5f),
                                        point3d(0.5f, 0.5f, 0.5f),
                                        point3d(0.5f, 0.5f, -0.5f)));
        résultat.ajouterTriangle(triangle(point3d(0.5f, -0.5f, 0.5f),
                                        point3d(0.5f, 0.5f, -0.5f),
                                        point3d(0.5f, -0.5f, -0.5f)));
        résultat.ajouterTriangle(triangle(point3d(-0.5f, -0.5f, 0.5f),
                                        point3d(0.5f, -0.5f, 0.5f),
                                        point3d(0.5f, -0.5f, -0.5f)));
        résultat.ajouterTriangle(triangle(point3d(-0.5f, -0.5f, 0.5f),
                                        point3d(0.5f, -0.5f, -0.5f),
                                        point3d(-0.5f, -0.5f, -0.5f)));
        résultat.ajouterTriangle(triangle(point3d(-0.5f, -0.5f, 0.5f),
                                        point3d(-0.5f, -0.5f, -0.5f),
                                        point3d(-0.5f, 0.5f, -0.5f)));
        résultat.ajouterTriangle(triangle(point3d(-0.5f, -0.5f, 0.5f),
                                        point3d(-0.5f, 0.5f, -0.5f),
                                        point3d(-0.5f, 0.5f, 0.5f)));
        résultat.ajouterTriangle(triangle(point3d(-0.5f, -0.5f, -0.5f),
                                        point3d(0.5f, -0.5f, -0.5f),
                                        point3d(0.5f, 0.5f, -0.5f)));
        résultat.ajouterTriangle(triangle(point3d(-0.5f, -0.5f, -0.5f),
                                        point3d(0.5f, 0.5f, -0.5f),
                                        point3d(-0.5f, 0.5f, -0.5f)));

        return résultat;

    }// FIN cube


    /** Défini la forme de l'objet comme une sphère */
    @Override
    public IObjet3d sphère(int résolution) {

        IObjet3d résultat = objet3D();

        // Vérification des entrées
        if (résolution >= 2) {

            // Déclaration / Initialisation de variables locales
            ArrayList<ArrayList<IPoint3d>> pointsSphère = new ArrayList<ArrayList<IPoint3d>>();
            ArrayList<IPoint3d> pointsDisqueCourant;

            // Déterminaison des points de la sphère

            // Initialisation de variables locales pour la déterminaison de points de la sphère
            Double x, y, z, r;

            // Initialisations pour la hauteur z courante
            Double alphaZ = -Math.PI / 2, alphaY;
            Integer i, j;
            i = 1;

            // Parcourt de la hauteur de la sphère
            while (alphaZ <= Math.PI / 2) {

                // Déterminaison de y
                y = Math.sin(alphaZ);

                // Initialisations pour le disque du z courant
                pointsDisqueCourant = new ArrayList<IPoint3d>();
                r = Math.cos(alphaZ);
                alphaY = 0d;
                j = 1;

                // Parcours du disque du z courant
                while (alphaY <= (Math.PI * 2)) {

                    // Déterminaison de x et z
                    x = r * Math.cos(alphaY);
                    z = -r * Math.sin(alphaY);

                    // Ajout du point au groupe des points
                    pointsDisqueCourant.add(point3d(x, y, z));

                    // Pas
                    alphaY = ((Math.PI * 2) / résolution) * j;
                    j++;

                }
                pointsSphère.add(pointsDisqueCourant);

                // Pas
                alphaZ = ((Math.PI * 2) / résolution) * i - (Math.PI * 2);
                i++;

            }

            // Construction des triangles en fonction des points de la sphère

            // Initialisations
            IPoint3d p1, p2, p3, p4;
            ArrayList<IPoint3d> disque1, disque2;

            // Parcours de tous les disques voisins
            for (int k = 0; k <= pointsSphère.size() - 2; k++) {

                // Récupération de deux disques voisins
                disque1 = pointsSphère.get(k);
                disque2 = pointsSphère.get(k + 1);

                // Parcours de tous les points voisins pour les deux disques
                for (Integer l = 0; l <= disque1.size() - 2; l++) {

                    // Détermination des points voisins
                    p1 = disque1.get(l);
                    p2 = disque1.get(l + 1);
                    p3 = disque2.get(l);
                    p4 = disque2.get(l + 1);

                    // Création des polygones avec les 4 points
                    résultat.ajouterTriangle(triangle(point3d(p1), point3d(p4), point3d(p3)));
                    résultat.ajouterTriangle(triangle(point3d(p1), point3d(p2), point3d(p4)));

                }

                // Cas limite
                p1 = disque1.get(disque1.size() - 1);
                p2 = disque1.get(0);
                p3 = disque2.get(disque2.size() - 1);
                p4 = disque2.get(0);

                // Création des polygones du cas limite
                résultat.ajouterTriangle(triangle(point3d(p1), point3d(p4), point3d(p3)));
                résultat.ajouterTriangle(triangle(point3d(p1), point3d(p2), point3d(p4)));

            }

        }

        return résultat;

    }// FIN defSphère


    @Override
    public IObjet3d cylindre(int résolution) {

        IObjet3d résultat = objet3D();

        // Vérification des entrées
        if (résolution >= 2) {

            // Déclaration / Initialisation de variables locales
            ArrayList<ArrayList<IPoint3d>> points = new ArrayList<ArrayList<IPoint3d>>();
            ArrayList<IPoint3d> pointsDisqueCourant;

            // Déterminaison des points du cylindre

            // Initialisation de variables locales
            Double x, y, z;
            Integer i;
            Double alphaY;

            // Parcourt du premier disque
            pointsDisqueCourant = new ArrayList<IPoint3d>();
            alphaY = 0d;
            i = 1;
            y = 0d;
            while (alphaY <= (Math.PI * 2)) {

                // Déterminaison de x et z
                x = Math.cos(alphaY);
                z = -Math.sin(alphaY);

                // Ajout du point au groupe des points
                pointsDisqueCourant.add(point3d(x, y, z));

                // Pas
                alphaY = ((Math.PI * 2) / résolution) * i;
                i++;

            }
            points.add(pointsDisqueCourant);

            // Parcourt du deuxième disque
            pointsDisqueCourant = new ArrayList<IPoint3d>();
            alphaY = 0d;
            i = 1;
            y = 2d;
            while (alphaY <= (Math.PI * 2)) {

                // Déterminaison de x et z
                x = Math.cos(alphaY);
                z = -Math.sin(alphaY);

                // Ajout du point au groupe des points
                pointsDisqueCourant.add(point3d(x, y, z));

                // Pas
                alphaY = ((Math.PI * 2) / résolution) * i;
                i++;

            }
            points.add(pointsDisqueCourant);


            // Construction des triangles en fonction des points de cylindre

            // Initialisations
            IPoint3d p1, p2, p3, p4;
            ArrayList<IPoint3d> disque1, disque2;

            // Récupération de deux disques
            disque1 = points.get(0);
            disque2 = points.get(1);

            // Parcours de tous les points voisins pour les deux disques
            for (i = 0; i <= disque1.size() - 2; i++) {

                // Détermination des points voisins
                p1 = disque1.get(i);
                p2 = disque1.get(i + 1);
                p3 = disque2.get(i);
                p4 = disque2.get(i + 1);

                // Création des polygones avec les 4 points
                résultat.ajouterTriangle(triangle(point3d(p1), point3d(p4), point3d(p3)));
                résultat.ajouterTriangle(triangle(point3d(p1), point3d(p2), point3d(p4)));
                résultat.ajouterTriangle(triangle(point3d(p2), point3d(p1), point3d(0, 0, 0)));
                résultat.ajouterTriangle(triangle(point3d(p3), point3d(p4), point3d(0, 2, 0)));
            }

            // Cas limite
            p1 = disque1.get(disque1.size() - 1);
            p2 = disque1.get(0);
            p3 = disque2.get(disque2.size() - 1);
            p4 = disque2.get(0);

            // Création des polygones du cas limite
            résultat.ajouterTriangle(triangle(point3d(p1), point3d(p4), point3d(p3)));
            résultat.ajouterTriangle(triangle(point3d(p1), point3d(p2), point3d(p4)));
            résultat.ajouterTriangle(triangle(point3d(p2), point3d(p1), point3d(0, 0, 0)));
            résultat.ajouterTriangle(triangle(point3d(p3), point3d(p4), point3d(0, 2, 0)));

        }

        return résultat;

    }// FIN defCylindre


    /** Défini la forme de l'objet comme un cône */
    @Override
    public IObjet3d cône(int résolution) {

        IObjet3d résultat = objet3D();

        // Vérification des entrées
        if (résolution >= 2) {

            // Déclaration / Initialisation de variables locales
            ArrayList<IPoint3d> points = new ArrayList<IPoint3d>();

            // Déterminaison des points du cône

            // Initialisation de variables locales
            Double x, y, z;
            Integer i;
            Double alphaY;

            // Parcourt du disque
            alphaY = 0d;
            i = 1;
            y = 0d;
            while (alphaY <= (Math.PI * 2)) {

                // Déterminaison de x et z
                x = Math.cos(alphaY);
                z = -Math.sin(alphaY);

                // Ajout du point au groupe des points
                points.add(point3d(x, y, z));

                // Pas
                alphaY = ((Math.PI * 2) / résolution) * i;
                i++;

            }


            // Construction des triangles en fonction des points du cône

            // Initialisations
            IPoint3d p1, p2, p3 = point3d(0, 2, 0);

            // Parcours de tous les points
            for (i = 0; i <= points.size() - 2; i++) {

                // Détermination des points voisins
                p1 = points.get(i);
                p2 = points.get(i + 1);

                // Création du polygone avec les 3 points
                résultat.ajouterTriangle(triangle(point3d(p1), point3d(p2), point3d(p3)));
                résultat.ajouterTriangle(triangle(point3d(p2), point3d(p1), point3d(0, 0, 0)));
            }

            // Cas limite
            p1 = (IPoint3d) points.get(points.size() - 1);
            p2 = (IPoint3d) points.get(0);

            // Création du polygone du cas limite
            résultat.ajouterTriangle(triangle(point3d(p1), point3d(p2), point3d(p3)));
            résultat.ajouterTriangle(triangle(point3d(p2), point3d(p1), point3d(0, 0, 0)));

        }

        return résultat;

    }// FIN cône


    /** Retourne un pixel */
    @Override
    public IPixel pixel() {

        return new SOGLPixel();

    }// FIN retPixel


    /** Retourne une image */
    @Override
    public IImage getImage(int largeur,
                           int hauteur) {

        return new SOGLImage(largeur, hauteur);

    }// FIN retImage


    /** Retourne un Affichage */
    @Override
    public Affichage affichage() {

        return SOGLAffichage.retSingleton();

    }// FIN affichage


    /** Retourne un univers */
    @Override
    public Univers univers() {

        // Construit un univers
        SOGLUnivers univers = new SOGLUnivers();

        // Le paramètre
        univers.setNbLumièresMax(nbLumièresMax);

        // Le retourne
        return univers;

    }// FIN univers


    /** Retourne une Caméra */
    @Override
    public Camera caméra() {

        return new SOGLCamera();

    }// FIN caméra


    /** Retourne un IObjet3d */
    @Override
    public IObjet3d objet3D() {

        return new Objet3D(this);

    }// FIN objet3D


    /** Retourne un matériau */
    @Override
    public Materiau matériau() {

        return new SOGLMateriau();

    }// FIN matériau


    /** {@inheritDoc} */
    @Override
    public AnimationObjets2D animationObjet2D() {
        return null; // TODO
    }// FIN animationObjet2D


    /** {@inheritDoc} */
    @Override
    public AnimationObjets3D animationObjet3D() {
        return null; // TODO
    }// FIN animationObjet3D


    /** {@inheritDoc} */
    @Override
    public CleObjet2D cléObjet2D() {
        return null; // TODO
    }// FIN cléObjet3D


    /** {@inheritDoc} */
    @Override
    public CleObjet3D cléObjet3D() {
        return null; // TODO
    }// FIN cléObjet3D


    /** Retourne une lumière ambiante */
    @Override
    public LumiereAmbiante lumièreAmbiante() {

        return new SOGLLumiereAmbiante();

    }// FIN lumièreAmbiante


    /** Retourne une ampoule */
    @Override
    public Ampoule ampoule() {

        return new SOGLAmpoule();

    }// FIN ampoule


    /** Retourne un spot */
    @Override
    public Spot spot() {

        return new SOGLSpot();

    }// FIN spot


    @Override
    public IApparence texture(IObjet3d objet3d) {

        return new Texture(this, objet3d);

    }


    @Override
    public IPositionTexture positionTexture(IApparence texture) {

        return new PositionTexture(this, texture);

    }


    @Override
    public IDimensionTexture dimensionTexture(IApparence texture) {

        return new DimensionTexture(this, texture);

    }


}// FIN Classe
